/**
 * 
 */
package co.edu.javeriana.ga.test.magicsquare;

import java.util.Set;
import java.util.TreeSet;

import org.jgap.FitnessFunction;
import org.jgap.IChromosome;

/**
 * <a> Fitness Function for Magic Cuadrant </a>
 * 
 * @author <a href="f.triana@javeriana.edu.co">Alejandro Triana</a>
 * @version 1.0
 */
public class MAFitness extends FitnessFunction {

	private static final long serialVersionUID = 1L;

	private Integer side;

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jgap.FitnessFunction#evaluate(org.jgap.IChromosome)
	 */
	@Override
	protected double evaluate(IChromosome chr) {

		double fitness = 0.0;

		fitness += evaluateRows(chr);

		fitness += evaluateColumns(chr);

		fitness += evaluateDiagonals(chr);

		fitness += evaluateRepetition(chr);

		if (fitness < 0) {
			fitness = 0;
		}

		return fitness;
	}

	/**
	 * <p>
	 * Evaluate the sum of columns
	 * </p>
	 */
	private int evaluateColumns(IChromosome chr) {

		int value = 0;

		for (int j = 0; j < side; j++) {

			double columnValue = 0.0;
			int columnIndex = j;

			for (int i = 0; i < side; i++) {
				columnValue += (Integer) chr.getGene(columnIndex).getAllele();
				columnIndex += side;
			}

			if (columnValue == valueSum()) {
				value += columnValue;
			} else {
				value -= valueSum();
			}
		}

		return value;
	}

	/**
	 * <p>
	 * Evaluate the sum of rows
	 * </p>
	 */
	private int evaluateRows(IChromosome chr) {

		int value = 0;
		int index = 0;

		for (int j = 0; j < side; j++) {
			double rowValue = 0.0;

			for (int i = 0; i < side; i++) {
				rowValue += (Integer) chr.getGene(index++).getAllele();
			}

			if (rowValue == valueSum()) {
				value += rowValue;
			} else {
				value -= valueSum();
			}
		}

		return value;
	}

	/**
	 * <p>
	 * Evaluate the sum of diagonals
	 * </p>
	 */
	private int evaluateDiagonals(IChromosome chr) {

		int value = 0;
		int index = 0;
		double diagonalValue = 0.0;

		for (int i = 0; i < side; i++) {
			diagonalValue += (Integer) chr.getGene(index).getAllele();
			index += (side + 1);
		}

		if (diagonalValue == valueSum()) {
			value += diagonalValue;
		} else {
			value -= valueSum();
		}

		diagonalValue = 0.0;
		index = side - 1;

		for (int i = 0; i < side; i++) {
			diagonalValue += (Integer) chr.getGene(index).getAllele();
			index += (side - 1);
		}

		if (diagonalValue == valueSum()) {
			value += diagonalValue;
		} else {
			value -= valueSum();
		}

		return value;
	}

	/**
	 * <p>
	 * Evaluate the repetitions into square
	 * </p>
	 */
	private double evaluateRepetition(IChromosome chr) {

		double value = 0;
		int numRepetitions = 0;

		Set<Integer> values = new TreeSet<Integer>();

		for (int i = 0; i < (side * side); i++) {
			Integer valueGen = (Integer) chr.getGene(i).getAllele();

			if (values.contains(valueGen)) {
				numRepetitions++;
			}
			
			values.add(valueGen);
		}

		if (numRepetitions > 0) {
			value = (-1 * numRepetitions) * valueSum();
		} else {
			value = valueSum();
		}
		
		return value;
	}

	/**
	 * <p>
	 * return the sum square value
	 * </p>
	 */
	public double valueSum() {
		double value = Math.pow(side, 2) + 1;
		return value;
	}

	/**
	 * @return side of quadrant
	 */
	public Integer getSide() {
		return side;
	}

	/**
	 * set side of quadrant
	 */
	public void setSide(Integer side) {
		this.side = side;
	}

}
